/*
OpenMVL Matrix Vector Library
Copyright (c) 2009 FluidInteractive (R)
 
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising
from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:
 
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must
not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
 
Written by: Nicola Candussi <nicola@fluidinteractive.com>
*/

//mat_mat_assign.hpp

#ifndef MVL_MAT_MAT_ASSIGN_HPP
#define MVL_MAT_MAT_ASSIGN_HPP

#include "layout.hpp"
#include "expr_order.hpp"

namespace mvl {

template<typename E1, typename E2, typename F>
struct mat_mat_assign 
{
    //the order is expr1's order
    //make sure expr2 has the same order
    typedef E1                                  expr1_t;
    typedef typename expr1_t::order             order;
    typedef typename expr1_t::layout_t          layout1_t;

    typedef const expr_order<order, const E2>   expr2_t;
    typedef typename expr2_t::layout_t          layout2_t;

    typedef typename layout::get2<
        typename layout1_t::Rows,
        typename layout1_t::Cols,
        order
    >::type size2;
    typedef layout::entry<
        typename layout::get1<meta::int_<0>, size2, order>::type,
        typename layout::get2<meta::int_<0>, size2, order>::type,
        layout::_0_
    > dummy_entry;    

    template<typename Entry1, typename Entry2, typename Tag2>
    struct entry { };

    struct element_assign {
        element_assign(expr1_t &e1, expr2_t const& e2) : m_e1(e1), m_e2(e2) {}
        expr1_t& m_e1;
        expr2_t const& m_e2;

        template<typename Entry1, typename Entry2>
        void operator()(entry<Entry1, Entry2, layout::_v_>) {
            F::apply(m_e1.template at<Entry1>(), m_e2.template at<Entry2>());
        }

        template<typename Entry1, typename Entry2>
        void operator()(entry<Entry1, Entry2, layout::_1_>) {
            F::apply(m_e1.template at<Entry1>(), typename expr2_t::value_type(1));
        }

        template<typename Entry1, typename Entry2>
        void operator()(entry<Entry1, Entry2, layout::_0_>) {
            F::apply(m_e1.template at<Entry1>(), typename expr2_t::value_type());
        }
    };

    struct row_assign {
        row_assign(expr1_t &e1, expr2_t const& e2) : m_e1(e1), m_e2(e2) {}
        expr1_t& m_e1;
        expr2_t const& m_e2;

        template<typename Row>
        void operator()(Row) {
            meta::for_each<
                typename meta::begin<Row>::type,
                typename meta::end<Row>::type
            >(element_assign(m_e1, m_e2));
        }
    };

    template<typename Data, typename It1, typename It2, typename Condition>
    struct state {
        typedef Data        data;
        typedef It1         it1;
        typedef It2         it2;
        typedef Condition   condition;
        typedef state       type;
    };

    //assign a row
    template<typename S>
    struct assign1 {
    
        typedef typename layout::begin2<typename S::it1>::type begin1;
        typedef typename layout::end2<typename S::it1>::type end1;
    
        typedef typename layout::begin2<typename S::it2>::type begin2;
        typedef typename layout::end2<typename S::it2>::type end2;

        struct update {
            template<typename State>
            struct apply {
                typedef typename meta::deref<typename State::it1>::type e1;
                typedef typename meta::eval_if<
                    typename meta::not_equal_to<typename State::it2, end2>::type,
                    meta::deref<typename State::it2>,
                    meta::identity<dummy_entry>                    
                >::type e2;
                typedef typename layout::get2<typename e1::i, typename e1::j, order>::type j1;
                typedef typename layout::get2<typename e2::i, typename e2::j, order>::type j2;

                typedef typename meta::eval_if<
                    typename meta::less_equal<j1, j2>::type,
                    meta::next<typename State::it1>,
                    meta::identity<typename State::it1>
                >::type new_it1;

                typedef typename meta::eval_if<
                    typename meta::less_equal<j2, j1>::type,
                    meta::next<typename State::it2>,
                    meta::identity<typename State::it2>
                >::type new_it2;

                typedef typename meta::eval_if<
                    typename meta::and_<
                        typename meta::equal_to<j1, j2>::type,
                        typename meta::is_same<typename e1::tag, layout::_v_>::type
                    >::type,
                    meta::push_back<
                        typename State::data, 
                        entry<e1, e2, typename e2::tag>
                    >,
                    meta::identity<typename State::data>
                >::type data;

                typedef typename meta::eval_if<
                    typename meta::and_<
                        typename meta::less<j1, j2>::type,
                        typename meta::is_same<typename e1::tag, layout::_v_>::type
                    >::type,
                    meta::push_back<
                        data, 
                        entry<
                            e1, 
                            dummy_entry,
                            layout::_0_
                        >
                    >,
                    meta::identity<data>
                >::type new_data;

                typedef state<
                    new_data,
                    new_it1,
                    new_it2,
                    typename meta::not_equal_to<new_it1, end1>::type
                > type;
            };
        };

        typedef typename meta::while_<
            state<
                meta::vector<>,
                begin1,
                begin2,
                typename meta::not_equal_to<begin1, end1>::type
            >,
            update
        >::type type;
    };
    //

    typedef typename layout::begin1<layout1_t>::type begin1;
    typedef typename layout::end1<layout1_t>::type end1;

    typedef typename layout::begin1<layout2_t>::type begin2;
    typedef typename layout::end1<layout2_t>::type end2;

    struct update {
        template<typename State>
        struct apply {
            typedef typename meta::next<typename State::it1>::type new_it1;
            typedef typename meta::next<typename State::it2>::type new_it2;
            typedef typename assign1<State>::type new_state1;
            typedef typename meta::push_back<
                typename State::data,
                typename new_state1::data
            >::type new_data;
            typedef state<
                new_data,
                new_it1,
                new_it2,
                typename meta::and_<
                    typename meta::not_equal_to<new_it1, end1>::type,
                    typename meta::not_equal_to<new_it2, end2>::type
                >::type
            > type;
        };
    };

    static inline void apply(expr1_t &e1, expr2_t const& e2)
    {
        typedef typename meta::while_<
            state<
                meta::vector<>,
                begin1,
                begin2,
                typename meta::and_<
                    typename meta::not_equal_to<begin1, end1>::type,
                    typename meta::not_equal_to<begin2, end2>::type
                >::type
            >,
            update
        >::type::data entries; 

        meta::for_each<
            typename meta::begin<entries>::type,
            typename meta::end<entries>::type
        >(row_assign(e1, e2));
    }
};

#define MVL_DEFINE_ASSIGN_FN(NAME, OP) \
template<typename T1, typename T2> \
struct fn_##NAME { \
    static void apply(T1 &lhs, T2 const& rhs) { lhs OP rhs; } \
}; 

MVL_DEFINE_ASSIGN_FN(assign, =)
MVL_DEFINE_ASSIGN_FN(add_equal, +=)
MVL_DEFINE_ASSIGN_FN(sub_equal, -=)
MVL_DEFINE_ASSIGN_FN(mul_equal, *=)
MVL_DEFINE_ASSIGN_FN(div_equal, /=)

#undef MVL_DEFINE_ASSIGN_FN

#define MVL_DEFINE_OPERATOR(NAME, OP) \
template<typename E1, typename E2>                                                      \
inline                                                                                  \
expr<E1>&                                                                               \
operator OP (expr<E1>& lhs, expr<E2> const& rhs) {                                      \
    typedef fn_##NAME<typename E1::value_type, typename E2::value_type> fn_type;        \
    mat_mat_assign<E1, E2, fn_type>::apply(lhs(), rhs());                            \
    return lhs;                                                                         \
}                                                                                       \

MVL_DEFINE_OPERATOR(add_equal, +=)
MVL_DEFINE_OPERATOR(sub_equal, -=)
MVL_DEFINE_OPERATOR(mul_equal, *=)
MVL_DEFINE_OPERATOR(div_equal, /=)

#undef MVL_DEFINE_OPERATOR

} // namespace mvl

#endif
